package paint;



/**
 * This class implements the DDA algorithm used to draw lines on the screen, as
 * well as the scanline algorithm used to fill solid objects.
 * 
 * @author Dustin Striplin
 *
 */
public class ScanlineContainer {
    	public static final int MIN_X_INDEX = 0;
    	public static final int MAX_X_INDEX = 1;
    	public static final int MIN_Z_INDEX = 2;
    	public static final int MAX_Z_INDEX = 3;
    	public static final int NUM_COORDINATES = 2;
    	
    	
    	private XZPair[][] mPoints;

    	
    	public ScanlineContainer(int height) {
    		mPoints = initializeContainer(height);
    	}
    	
    	/**
    	 * @return the underlying data structure.
    	 */
    	public XZPair[][] getContainer() {
    		return mPoints;
    	}
    	
    	/**
    	 * Add a 2d line to the data structure
    	 * @param a an end point on the line
    	 * @param b an end point on the line
    	 */
    	public void addLine(Point3D a, Point3D b) {
    		addDDA(a, b);
    	}
    	
    	private XZPair[][] initializeContainer(int height) {
    		XZPair[][] container = new XZPair[height][NUM_COORDINATES];
    		for(int y = 0; y < height; y++) {
    			for(int x = 0; x < NUM_COORDINATES; x++) {
    				container[y][x] = new XZPair();
    			}
    		}
    		
    		return container;
    	}
    	
    	/**
    	 * perform the dda algorithm on the line and add each point to the data structure
    	 * @param a an end point on the line
    	 * @param b an end point on the line
    	 */
    	private void addDDA(Point3D a, Point3D b) {
    		//determine left and right most points
    		Point3D left = a;
    		Point3D right = b;

    		if(a.x > b.x){
    			left = b;
    			right = a;
    		}
    		double xzSlope = calculateXZSlope(left, right);
    		double m = calculateSlope(left,right);

    		//if slope is <= 1 use y = x
    		if(m <= 1 && m >=-1) { //y is dependent variable, x is independent
    			double y = left.y - m;
    			double z = left.z - xzSlope;
    			
	    		for(int x = left.x; x <= right.x; x++) {
	    			y+=m;
	    			z+=xzSlope;
	    			int roundedY = (int)Math.round(y);
	    			if(roundedY < mPoints.length && roundedY > -1) {
		    			if(mPoints[roundedY][MIN_X_INDEX].x == XZPair.UNINITIALIZED || mPoints[roundedY][MIN_X_INDEX].x >= x) {
		    				mPoints[roundedY][MIN_X_INDEX].x = x;
		    				mPoints[roundedY][MIN_X_INDEX].z = z; 
		    			}
		    			if(mPoints[roundedY][MAX_X_INDEX].x <= x) {
		    				mPoints[roundedY][MAX_X_INDEX].x = x;
		    				mPoints[roundedY][MAX_X_INDEX].z = z;
		    			}
	    			}
	    			
	    		}
    		} else { //x is dependent variable, y is independent

        		if(a.y > b.y){
        			left = b;
        			right = a;
        		} else {
        			left = a;
        			right = b;

        		}
    			double yzSlope = calculateYZSlope(left,right);
    			m = 1/m;//set slope to it's inverse
    			//
    			double x = left.x - m;
    			double z = left.z - yzSlope;
	    		for(int y = left.y; y <= right.y; y++) {
	    			x+=m;
	    			z+=yzSlope;
	    			int roundedX = (int)Math.round(x);
	    			if(y < mPoints.length && y > -1) {
		    			if(mPoints[y][MIN_X_INDEX].x == XZPair.UNINITIALIZED || mPoints[y][MIN_X_INDEX].x >= roundedX) {
		    				mPoints[y][MIN_X_INDEX].x = roundedX;
		    				mPoints[y][MIN_X_INDEX].z = z;
		    			}
		    			if(mPoints[y][MAX_X_INDEX].x <= roundedX) {
		    				mPoints[y][MAX_X_INDEX].x = roundedX;
		    				mPoints[y][MAX_X_INDEX].z = z; 
		    			}
	    			}
	    		}
    		}
    	}
    	
    	
    	private double calculateSlope(Point3D left, Point3D right) {
    		double rise = right.y - left.y;
    		double run = right.x - left.x;
    		if(run == 0) {
    			run =  Double.MIN_VALUE;
    		}
    		return rise/run;
    	}
    	
    	/**
    	 * the slope of the values
    	 * @param p1
    	 * @param p2
    	 * @return (p2.z - p1.z)/(p2.x - p1.x)
    	 */
    	private double calculateXZSlope(Point3D p1, Point3D p2) {
    		double rise = p2.z - p1.z;
    		double run = p2.x - p1.x;
    		if(run == 0) {
    			run = Double.MIN_VALUE;
    		}
    		return rise/run;
    	}
    	
    	private double calculateYZSlope(Point3D p1, Point3D p2) {
    		double rise = p2.z - p1.z;
    		double run = p2.y - p1.y;
    		if(run == 0) {
    			run = Double.MIN_VALUE;
    		}
    		return rise/run;
    	}
    	
    	
    	@Override
    	public String toString() {
    		StringBuilder sb = new StringBuilder();
    		for(int line = mPoints.length-1; line >= 0; line--) {
    			for(int x = 0; x < mPoints[line].length; x++) {
    				sb.append(mPoints[line][x]);
    				sb.append(", ");
    			}
    			sb.append("\n");
    		}
    		return sb.toString();
    	}
    }
